/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 attack.cpp

	$Header: /heroes4/attack.cpp $

	$NoKeywords: $

 ************************************************************************/

#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "attack.h"

#include "adaptor_handler.h"
#include "battlefield.h"
#include "bound_handler.h"
#include "can_cast.h"
#include "check_artifact_influence.h"
#include "combat_creature.h"
#include "combat_spell.h"
#include "combat_spell_registration.h"
#include "creature_ability.h"
#include "creature_stack.h"
#include "damage_type.h"
#include "delay_effect.h"
#include "elements_of.h"
#include "external_string.h"
#include "function_handler.h"
#include "get_artifact_damage_modifier.h"
#include "magic_mirror.h"
#include "random.h"
#include "replace_keywords.h"
#include "spell.h"
#include "spell_properties.h"
#include "stoning_animation.h"

extern t_external_string const k_combat_action_cause_effect( "combat_action.cause_effect" );
static t_external_string const k_combat_action_devour("combat_action.devour");
static t_external_string const k_combat_action_freeze_enemy( "combat_action.freeze_enemy" );

// ----------------------------------------------------------
// Action message for an attack-caused effect
// ----------------------------------------------------------
static std::string get_effect_text( t_spell effect )
{
	return replace_keywords( k_combat_action_cause_effect, "%spell", get_short_spell_name( effect ) );
}


namespace
{
	// --------------------------------------------------------------
	// class to handle devouring of a creature
	// --------------------------------------------------------------
	class t_devour_end : public t_handler_base_1<t_window*>
	{
	public:
		t_devour_end( t_battlefield& battlefield, t_combat_creature& creature, int number, t_combat_action_message const & message );
		virtual void operator()( t_window* window );
	protected:
		t_battlefield&			m_battlefield;
		t_combat_creature&		m_creature;
		t_combat_action_message	m_message;
		int						m_number;
	};

	class t_devour : public t_handler_base
	{
	public:
		t_devour( t_battlefield& battlefield, t_combat_creature & attacker, t_combat_creature& creature, int number );

		virtual void operator()();
	protected:
		t_combat_creature&  m_attacker;
		t_battlefield&		m_battlefield;
		t_combat_creature&	m_creature;
		int					m_number;
	};

};

	// --------------------------------------------------------------
	// class to handle devouring of a creature
	// --------------------------------------------------------------
t_devour_end::t_devour_end( t_battlefield& battlefield, t_combat_creature& creature, int number, t_combat_action_message const & message )
        : m_battlefield(battlefield), m_creature( creature ), m_number( number ), m_message( message )
{
}

void t_devour_end::operator ()( t_window* window )
{
	if (m_number > m_creature.get_number())
		m_number = m_creature.get_number();
	m_creature.kill_permanently( m_number, m_message );
	m_battlefield.end_spell( window, m_creature );
}

t_devour::t_devour( t_battlefield& battlefield, t_combat_creature & attacker, t_combat_creature& creature, int number )
        : m_attacker( attacker ), m_battlefield( battlefield ), m_creature( creature ), m_number( number )
{
}

void t_devour::operator ()()
{
	t_combat_action_message message( m_attacker, k_combat_action_devour );

	t_window_handler handler = new t_devour_end( m_battlefield, m_creature, m_number, message );

	m_battlefield.start_animation( m_creature, k_spell_effect_devour, handler, message );
}

// ----------------------------------------------------------
// class to execute a fire shield attack
// ----------------------------------------------------------
namespace
{
	class t_fire_shield : public t_delayed_effect
	{
	public:
		t_fire_shield( t_battlefield& battlefield,
					   t_combat_creature* attacker,
		               t_combat_creature* defender, int damage, t_spell spell );

		virtual void execute( t_window* window );
		virtual void operator()();
	protected:
		t_combat_creature_ptr	m_attacker;
		t_battlefield&			m_battlefield;
		t_combat_creature_ptr	m_defender;
		int						m_damage;
		t_spell					m_spell;
	};
};

// ----------------------------------------------------------
// class to execute a fire shield attack
// ----------------------------------------------------------
t_fire_shield::t_fire_shield( t_battlefield& battlefield, t_combat_creature* attacker,
							  t_combat_creature* defender, int damage, t_spell spell )
			 : m_battlefield( battlefield )
{
	m_attacker = attacker;
	m_defender = defender;
	m_damage = damage;
	m_spell  = spell;
}

// ----------------------------------------------------------
// class to execute a fire shield attack
// ----------------------------------------------------------
void t_fire_shield::execute( t_window* window )
{
	t_combat_action_message message( *m_defender, get_short_spell_name( m_spell ) );
	
	m_attacker->add_damage( m_damage, m_defender, false, false, message);
	m_attacker->check_flinch( message );
	m_attacker->apply_damage( m_battlefield, true );
	m_battlefield.end_spell( window, *m_defender );
}

// ----------------------------------------------------------
// class to execute a fire shield attack
// ----------------------------------------------------------
void t_fire_shield::operator ()()
{
	if (m_attacker->get_number() == 0)
		return;

	t_window_handler handler;
	t_combat_action_message message( *m_defender, get_short_spell_name( m_spell ) );

	handler = new t_delay_effect( this );
	m_battlefield.start_animation( *m_defender, m_spell, handler, message );
}


// ----------------------------------------------------------
// record details of an attack for later resolution
// ----------------------------------------------------------
t_attack::t_attack( t_battlefield&				battlefield, 
				    t_combat_creature*			attacker, 
					t_abstract_combat_object*	defender, 
					bool						ranged,
				    int							damage )
		: m_battlefield( battlefield )
{
	m_attacker = attacker;
	m_defender = defender;
	m_damage   = damage;
	m_ranged   = ranged;
	m_number_of_attackers = attacker->get_number();
}


// ----------------------------------------------------------
// record details of an attack for later resolution
// ----------------------------------------------------------
void t_attack::check_random_curse()
{
	if (!m_attacker->has_ability( k_ability_random_curse ))
		return;

	t_combat_creature* defender = dynamic_cast<t_combat_creature*>( m_defender.get() );
	if (defender == 0)
		return;

	std::list<t_spell> spells;
	int				   i;
	int				   count;
	t_spell const      k_spells[] =
	{
		k_spell_misfortune,
		k_spell_cancellation,
		k_spell_curse,
		k_spell_ray_of_suffering,
		k_spell_slow,
		k_spell_sorrow,
		k_spell_weakness,
		k_spell_cowardice,
		k_spell_forgetfulness,
	};

	count = 0;
	for ( i = 0; i < ELEMENTS_OF( k_spells ); i++)
	{
		if (!can_affect( *m_attacker, k_spells[i], *defender ))
			continue;
		if (defender->is_active( k_spells[i] ))
			continue;
		spells.push_back( k_spells[i] );
		count++;
	}
	if (spells.empty())
		return;

	std::list<t_spell>::iterator index;

	count = random( count );
	for ( index = spells.begin(); index != spells.end(); index++ )
	{
		count--;
		if (count < 0)
			break;
	}

	cast_and_mirror( m_attacker, defender, *index );
}

// ----------------------------------------------------------
// check for creature freezing
// ----------------------------------------------------------
static void start_freeze( t_combat_creature_ptr attacker, t_combat_creature_ptr defender )
{
	if (defender->get_number() > 0)
	{
		t_combat_action_message message( *attacker, k_combat_action_freeze_enemy );

		defender->set_spell( k_spell_frozen, message );

		t_freeze_animation_ptr animation = new t_freeze_animation( *defender );
		animation->display_action_message( message );
		defender->set_animation( animation );
	}
}

// ----------------------------------------------------------
// check for creature freezing
// ----------------------------------------------------------
static void check_freeze( t_battlefield& battlefield, t_combat_creature_ptr attacker, 
						  t_combat_creature_ptr defender )
{
	if (!attacker->has_ability( k_ability_freeze ))
		return;
	if (!can_affect( k_spell_frozen, *defender ))
		return;

	if (random(100) >= 30)
		return;

	if (!battlefield.check_resistance( *attacker, *defender, k_spell_frozen ))
		return;
	
	t_handler handler;

	handler = add_argument( add_1st_argument( function_2_handler( start_freeze ), attacker), defender );
	battlefield.add_action( handler, true );
}

static t_external_string const k_combat_action_stone_gaze( "combat_action.stone_gaze" );

// ----------------------------------------------------------
// record details of an attack for later resolution
// ----------------------------------------------------------
static void start_stone_gaze( t_combat_creature_ptr attacker, t_combat_creature_ptr defender, int killed )
{
	if (killed > defender->get_number())
		killed = defender->get_number();
	if (killed > 0)
	{
		t_combat_action_message message( *attacker, k_combat_action_stone_gaze );
		t_stoning_animation_ptr animation = new t_stoning_animation( *defender, killed );
		t_battlefield&			battlefield = defender->get_battlefield();

		battlefield.start_animation( *defender, k_spell_stone_gaze, message );
		animation->display_action_message( message );
		defender->set_animation( animation );
	}
}

void t_attack::check_stone_gaze()
{
	if (!m_attacker->has_ability( k_ability_stone_gaze ))
		return;

	t_combat_creature* defender = dynamic_cast<t_combat_creature*>( m_defender.get() );
	if (defender == 0)
		return;

	int killed = 0;
	int i;
	int stoning_chance = m_attacker->get_stoning_chance( *defender );

	if (stoning_chance * m_number_of_attackers < 500)
		return;

	if (m_number_of_attackers > 100)
	{
		for (i = 0; i < 100; i++)
		{
			if (random(1000) < stoning_chance)
				killed++;
		}
		killed = killed * m_number_of_attackers / 100;
	}
	else
	{
		for (i = 0; i < m_number_of_attackers; i++)
		{
			if (random(1000) < stoning_chance)
				killed++;
		}
	}
	if (killed == 0)
		return;

	// resistance was already checked by get_stoning_chance, so we don't
	// need to call it again.

	t_handler			  handler;
	t_combat_creature_ptr ptr = defender;

	handler = add_argument( add_1st_argument( add_1st_argument( function_3_handler( start_stone_gaze ), 
		                    m_attacker), ptr ), killed );
	m_battlefield.add_action( handler, true );
}

// ----------------------------------------------------------
// evaluate and resolve magic artifact damage
// ----------------------------------------------------------
static void check_magic_damage( t_battlefield& battlefield, 
							    t_combat_creature* attacker, t_combat_creature* defender, 
								bool ranged )
{
	t_hero* hero = attacker->get_hero();

	if (hero == 0)
		return;

	std::vector<int> damage;

	if (get_artifact_damage( *hero, ranged, damage ) == 0)
		return;

	// only lightning and fire currently properly supported
	if (damage[k_damage_lightning] > 0 && can_affect( k_spell_lightning, *defender ))
	{
		t_combat_spell_ptr spell;

		spell = create_combat_spell( battlefield, k_spell_lightning );
		spell->cast_and_mirror( defender, damage[k_damage_lightning] );
	}
	if (damage[k_damage_fire] > 0 && can_affect( k_spell_fire_aura_effect, *defender ))
	{
		int fire_damage;

		fire_damage = defender->modify_spell_damage( 0, damage[k_damage_fire], k_spell_fire_aura_effect );
		battlefield.add_action( new t_fire_shield( battlefield, defender, 
												   attacker, fire_damage,
												   k_spell_firebreathing ), true );
	}
}

// ----------------------------------------------------------
// record details of an attack for later resolution
// ----------------------------------------------------------
void t_attack::resolve()
{
	t_combat_creature* defender;

	defender = dynamic_cast<t_combat_creature*>( m_defender.get() );
	if (defender != 0)
	{
		if (m_attacker->is_active( k_spell_poison_attack )
			&& can_affect( *m_attacker, k_spell_poison, *defender ))
		{
			int damage = m_attacker->get_effect_power( k_spell_poison_attack );

			if (defender->has_ability( k_ability_death_protection ))
				damage >>= 1;
			defender->add_poison( damage, *m_attacker );
		}
	}
	if (!m_ranged && defender != 0)
	{
		if (defender->has_ability( k_ability_fire_shield )
			&& can_affect( k_spell_fire_shield, *m_attacker ))
		{
			int damage = (m_damage + 3) >> 2;

			if (defender->has_ability( k_ability_chaos_protection ))
				damage = (damage + 1) >> 1;

			m_battlefield.add_action( new t_fire_shield( m_battlefield, m_attacker, 
													   defender, damage,
													   k_spell_fire_shield ), true );
		}
		if (defender->is_active( k_spell_holy_retribution )
			&& can_affect( k_spell_holy_retribution, *m_attacker))
		{
			int damage = defender->get_effect_power( k_spell_holy_retribution );
			
			damage = m_attacker->modify_spell_damage( 0, damage, k_spell_holy_retribution );
			if (damage > 0)
				m_battlefield.add_action( new t_fire_shield( m_battlefield, m_attacker,
				                            defender, damage, k_spell_holy_retribution ), true );
		}
	}

	t_attackable_object* object = m_defender->get_attackable_object();
	
	object->apply_damage( m_battlefield, true );
	if (!object->is_alive())
		return;

	check_random_curse();
	check_stone_gaze();
	if (defender != 0)
	{
		if (m_attacker->has_ability( k_ability_weakness ) && defender->is_living_creature())
		{
			cast_and_mirror( m_attacker, defender, k_spell_weakness );
		}

		check_artifact_spell_attack( *m_attacker, *defender, m_ranged );
		check_magic_damage( m_battlefield, m_attacker, defender, m_ranged );

		// check stunning
		if (defender->is_living_creature())
		{
			if ((m_attacker->has_ability( k_ability_stunning ) && !m_ranged)
				|| (m_attacker->has_ability( k_ability_ranged_stun ) && m_ranged))
			{
				int total_hits = defender->get_total_hits() + m_damage;

				if (random(100) < m_damage * 100 / total_hits - 5)
				{
					t_combat_action_message message( *m_attacker, get_effect_text( k_spell_stun ) );
					m_battlefield.start_animation( *defender, k_spell_stun, message );
					defender->set_spell( k_spell_stun, message );
				}
			}
		}
	}
	if (!m_ranged && defender != 0)
	{
		if (m_attacker->has_ability( k_ability_aging ))
		{
			cast_and_mirror( m_attacker, defender, k_spell_aging );
		}

		if (m_attacker->has_ability( k_ability_blinding ))
		{
			if (random(100) < 30)
			{
				cast_and_mirror( m_attacker, defender, k_spell_blind );
			}
		}

		if (m_attacker->has_ability( k_ability_curse ))
		{
			cast_and_mirror( m_attacker, defender, k_spell_curse );
		}

		check_freeze( m_battlefield, m_attacker, defender );
		if (m_attacker->has_ability( k_ability_devouring ))
		{
			int devoured = 0;
			int i;

			if (m_number_of_attackers > 100)
			{
				for (i = 0; i < 100; i++)
				{
					if (random(10) == 0)
						devoured++;
				}
				devoured = devoured * m_number_of_attackers / 100;
			}
			else
			{
				for (i = 0; i < m_number_of_attackers; i++)
				{
					if (random(10) == 0)
						devoured++;
				}
			}
			if (devoured > (m_number_of_attackers + 9) / 10)
				devoured = (m_number_of_attackers + 9) / 10;
			if (devoured > 0)
				m_battlefield.add_action( new t_devour( m_battlefield, *m_attacker, *defender, devoured ), true );
		}

		if ( m_attacker->has_ability( k_ability_hypnotize )	&& random(10) < 3 )
		{
			if (m_battlefield.check_resistance( *m_attacker, *defender, k_spell_short_hypnotism ))
			{
				t_combat_action_message message( *m_attacker, get_effect_text( k_spell_short_hypnotism ) );

				defender->set_spell( k_spell_short_hypnotism, message );
				m_battlefield.start_animation( *defender, k_spell_short_hypnotism, message );
			}
		}
		
		if (m_attacker->has_ability( k_ability_binding ) && m_attacker->get_number() > 0
			&& !defender->has_ability( k_ability_insubstantial ))
		{
			t_combat_action_message message( *m_attacker, 
											 replace_keywords( k_combat_action_cause_effect, "%spell", get_short_spell_name( k_spell_binding ) ) );
			m_attacker->set_binding( *defender, message );
		}
		
		if (m_attacker->has_ability( k_ability_lightning )
			&& can_affect( k_spell_lightning, *defender ))
		{
			int				   damage = 30 * m_number_of_attackers;
			t_combat_spell_ptr spell;

			spell = create_combat_spell( m_battlefield, k_spell_lightning );
			spell->cast_and_mirror( defender, damage );
		}
		if (m_attacker->is_active( k_spell_firebreathing )
			&& can_affect( k_spell_fire_aura_effect, *defender ))
		{
			int damage;
			
			damage = m_attacker->get_effect_power( k_spell_firebreathing );
			damage = defender->modify_spell_damage( 0, damage, k_spell_fire_aura_effect );
			m_battlefield.add_action( new t_fire_shield( m_battlefield, defender, 
													   m_attacker, damage,
													   k_spell_firebreathing ), true );
		}
	}
}

namespace
{
	class t_vampirism : public t_handler_base_1<t_window*>
	{
	public:
		t_vampirism( t_battlefield& battlefield, t_combat_creature& creature, int amount );
		virtual void operator()( t_window* window );
	protected:
		t_battlefield&		m_battlefield;
		t_combat_creature&  m_creature;
		int					m_amount;
	};
};

inline t_vampirism::t_vampirism( t_battlefield& battlefield, t_combat_creature& creature, 
								 int amount )
				  : m_battlefield( battlefield ), m_creature( creature ), m_amount( amount )
{
}

void t_vampirism::operator ()( t_window* window )
{
	m_creature.resurrect( m_amount );
	m_battlefield.end_spell( window, m_creature );
}

static t_external_string const k_combat_action_vampirism( "combat_action.vampirism" );

// ----------------------------------------------------------
// record details of an attack for later resolution
// ----------------------------------------------------------
void t_attack::check_vampirism()
{
	if (m_attacker->get_number() == 0)
		return;
	if (m_ranged)
		return;

	t_combat_creature* defender = dynamic_cast<t_combat_creature*>(m_defender.get());
	t_battlefield&		battlefield = m_battlefield;

	if (defender == 0 || !defender->is_living_creature())
		return;
	if (!m_attacker->has_ability( k_ability_vampire ) 
		&& !m_attacker->is_active( k_spell_vampiric_touch ))
		return;
	if (m_attacker->get_wounds() == 0 
		&& m_attacker->get_number() >= m_attacker->get_creature_stack()->get_number())
		return;

	int healed;
	
	healed = (m_damage + 1) >> 1;

	if (defender->has_ability( k_ability_death_protection ))
	{
		healed = (healed + 1) >> 1;
	}
	
	if (m_attacker->has_ability( k_ability_vampire ))
		m_attacker->resurrect( healed );
	else
	{
		t_window_handler end_handler;

		t_combat_action_message message( *m_attacker, k_combat_action_vampirism );

		end_handler = new t_vampirism( m_battlefield, *m_attacker, healed );
		battlefield.start_animation( *m_attacker, k_spell_effect_vampirism, end_handler, message );
	}
}
